﻿using System;
using System.IO;

namespace MD5
{
    class MD5
    {
        public MD5()
        {
			AutoReset = false;
        }

        public byte[] ComputeHash(Stream reader)
        {
            long fileSize = reader.Length;
            // Długość wiadomości w bitach
            ulong msgBitSize = (ulong)fileSize << 3;
            byte[] block = new byte[64];
            uint[] words = new uint[16];
            block[0] = 1;
            long diff = fileSize - reader.Position;

            // Odczytujemy kolejne bloki z pliku i przetwarzamy
            while (reader.Position < fileSize)
            {
                if (64 <= diff)
                    reader.Read(block, 0, 64);
                else
                    break;
                Convert(block, words, false);
                Transform(words);
                diff = fileSize - reader.Position;
            }

            Array.Clear(block, 0, 64);
            if (diff > 0)
                reader.Read(block, 0, (int)diff);
            // Dostawiamy jedynkę na końcu wiadomości
            block[diff] = 0x80;
            // Jeśli nie starczyło miejsca na licznik znaków w wiadomości...
            if (64 - (diff + 1) < 8)
            {
                Convert(block, words, false);
                Transform(words);
                Array.Clear(block, 0, 64);
            }

            // Wstawiamy licznik na końcu bloku
            block[56] = (byte)(msgBitSize >> 56);
            block[57] = (byte)((msgBitSize >> 48) & 0xFF);
            block[58] = (byte)((msgBitSize >> 40) & 0xFF);
            block[59] = (byte)((msgBitSize >> 32) & 0xFF);
            block[60] = (byte)((msgBitSize >> 24) & 0xFF);
            block[61] = (byte)((msgBitSize >> 16) & 0xFF);
            block[62] = (byte)((msgBitSize >> 8) & 0xFF);
            block[63] = (byte)(msgBitSize & 0xFF);

            Convert(block, words, true);
            Transform(words);

            reader.Dispose();
            // Przepisujemy hash na bajty.
            byte[] hash = new byte[16];
            Array.Copy(BitConverter.GetBytes(h0), 0, hash, 0, 4);
            Array.Copy(BitConverter.GetBytes(h1), 0, hash, 4, 4);
            Array.Copy(BitConverter.GetBytes(h2), 0, hash, 8, 4);
            Array.Copy(BitConverter.GetBytes(h3), 0, hash, 12, 4);

            return hash;
        }
		
		public void Reset()
        {
            h0 = 0x67452301;
            h1 = 0xefcdab89;
            h2 = 0x98badcfe;
            h3 = 0x10325476;
        }

        public bool AutoReset { get; set; }
		
		private void Transform(uint[] words)
        {
            uint a = h0, b = h1, c = h2, d = h3;

            for (int i = 0; i < 16; ++i)
            {
                uint f = (b & c) | (~b & d);
                uint tmp = d;
                d = c;
                c = b;
                uint s = a + f + K[i] + words[i];
                b = b + ((s << S[i]) | (s >> (32 - S[i])));
                a = tmp;
            }

            for (int i = 16; i < 32; ++i)
            {
                uint f = (d & b) | (~d & c);
                int g = (5 * i + 1) % 16;
                uint tmp = d;
                d = c;
                c = b;
                uint s = a + f + K[i] + words[g];
                b = b + ((s << S[i]) | (s >> (32 - S[i])));
                a = tmp;
            }

            for (int i = 32; i < 48; ++i)
            {
                uint f = b ^ c ^ d;
                int g = (3 * i + 5) % 16;
                uint tmp = d;
                d = c;
                c = b;
                uint s = a + f + K[i] + words[g];
                b = b + ((s << S[i]) | (s >> (32 - S[i])));
                a = tmp;
            }

            for (int i = 48; i < 64; ++i)
            {
                uint f = c ^ (b | ~d);
                int g = (7 * i) % 16;
                uint tmp = d;
                d = c;
                c = b;
                uint s = a + f + K[i] + words[g];
                b = b + ((s << S[i]) | (s >> (32 - S[i])));
                a = tmp;
            }

            h0 += a;
            h1 += b;
            h2 += c;
            h3 += d;
        }

        private static void Convert(byte[] bytes, uint[] words, bool last)
        {
            int iters = last ? 14 : 16;

            for (int i = 0; i < iters; ++i)
                words[i] = ((uint)bytes[(i << 2) + 3] << 24) | ((uint)bytes[(i << 2) + 2] << 16) | ((uint)bytes[(i << 2) + 1] << 8) | ((uint)bytes[i << 2]);

            if (last)
            {
                words[15] = ((uint)bytes[56] << 24) | ((uint)bytes[57] << 16) | ((uint)bytes[58] << 8) | ((uint)bytes[59]);
                words[14] = ((uint)bytes[60] << 24) | ((uint)bytes[61] << 16) | ((uint)bytes[62] << 8) | ((uint)bytes[63]);
            }
        }

        private static byte[] S =
        {
            7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
            5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,
            4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,
            6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21
        };

        private static uint[] K =
        {
            0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
            0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
            0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
            0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
            0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
            0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
            0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
            0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
            0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
            0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
            0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
            0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
            0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
            0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
            0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
            0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
        };

        private uint h0 = 0x67452301;
        private uint h1 = 0xefcdab89;
        private uint h2 = 0x98badcfe;
        private uint h3 = 0x10325476;
     
    }

}
